using System;
using System.Collections.Generic;
using System.Threading;

namespace Firefly.Framework.Collections.Specialized
{
    public class ObjectPool : IDisposableEx
    {
        private object _locker;
        private Dictionary<Type, Stack<IPoolableObject>> _availableObjects;
        private Dictionary<Type, List<IPoolableObject>> _usedObjects;

        public ObjectPool()
        {
            _locker = new object();
            _availableObjects = new Dictionary<Type, Stack<IPoolableObject>>();
            _usedObjects = new Dictionary<Type, List<IPoolableObject>>();
        }

        public void SeedBuffer<T>(int seed) where T : class, IPoolableObject, new()
        {
            lock (_locker)
            {
                Type type = typeof(T);

                if (_usedObjects.ContainsKey(type) == false && _availableObjects.ContainsKey(type) == false)
                {
                    _availableObjects.Add(type, new Stack<IPoolableObject>());
                    _usedObjects.Add(type, new List<IPoolableObject>());

                    for (int i = 0; i < seed; i++)
                    {
                        _availableObjects[type].Push(new T());
                    }
                }
            }
        }

        public int GetUsedCount<T>() where T : class, IPoolableObject, new()
        {
            Type type = typeof(T);

            if (_usedObjects != null)
            {
                return _usedObjects[type].Count;
            }
            else
            {
                return 0;
            }
        }

        public int GetAvailableCount<T>() where T : class, IPoolableObject, new()
        {
            Type type = typeof(T);

            if (_availableObjects != null)
            {
                return _availableObjects[type].Count;
            }
            else
            {
                return 0;
            }
        }

        public T Aquire<T>() where T : class, IPoolableObject, new()
        {
            lock (_locker)
            {
                T obj = Get<T>();
                Add<T>(typeof(T), obj);
                return obj;
            }
        }

        public void Release<T>(T item) where T : class, IPoolableObject, new()
        {
            lock (_locker)
            {
                Type type = typeof(T);

                if (_usedObjects.ContainsKey(type) == false || _availableObjects.ContainsKey(type) == false)
                {
                    throw new ArgumentException(String.Format("The object type '{0}' is not contained in the object pool.", type.ToString()));
                }

                item.Reset();
                _usedObjects[type].Remove(item);
                _availableObjects[type].Push(item);
            }
        }

        public void Reset()
        {
            lock (_locker)
            {
                foreach (Type type in _usedObjects.Keys)
                {
                    for (int i = 0; i < _usedObjects[type].Count; i++)
                    {
                        object item = _usedObjects[type][i];
                        _usedObjects[type].RemoveAt(i);
                        _availableObjects[type].Push((IPoolableObject)item);
                    }
                }
            }
        }

        private void Add<T>(Type type, T item) where T : class, IPoolableObject, new()
        {
            if (_usedObjects.ContainsKey(type) == false)
            {
                _usedObjects.Add(type, new List<IPoolableObject>());
            }

            _usedObjects[type].Add(item);
        }

        private T Get<T>() where T : class, IPoolableObject, new()
        {
            Type type = typeof(T);

            if (_availableObjects.ContainsKey(type) == false)
            {
                _availableObjects.Add(type, new Stack<IPoolableObject>());
            }

            T obj;

            if (_availableObjects[type].Count == 0)
            {
                obj = new T();
            }
            else
            {
                obj = _availableObjects[type].Pop() as T;
            }

            return obj;
        }

        #region IDisposableEx Members

        public bool Disposed { get; protected set; }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            if (!Disposed)
            {
                if (disposing)
                {
                    if (_availableObjects != null)
                    {
                        _availableObjects.Clear();
                        _availableObjects = null;
                    }

                    if (_usedObjects != null)
                    {
                        _usedObjects.Clear();
                        _usedObjects = null;
                    }
                }

                Disposed = true;
            }
        }

        ~ObjectPool()
        {
            Dispose(false);
        }

        #endregion
    }
}
